package hos.location;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.location.Address;
import android.location.Geocoder;
import android.location.LocationManager;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;

import hos.map.coordinate.CoordinateType;


/**
 * <p>Title: LocationDataSource </p>
 * <p>Description:  </p>
 * <p>Company: www.mapuni.com </p>
 *
 * @author : 蔡俊峰
 * @version : 1.0
 * @date : 2020/10/17 23:06
 */
public abstract class LocationSource implements LocationSourceListener {

    private final AtomicReference<Status> mStartStatus;
    private LocationData mCurrentLocation;
    private LocationData mLastKnownLocation;
    private Throwable mThrowable;
    private final List<LocationChangedListener> mLocationChangedListeners = new CopyOnWriteArrayList<>();
    private final List<LocationStatusChangedListener> mStatusChangedListeners = new CopyOnWriteArrayList<>();
    private final List<LocationHeadingChangedListener> mHeadingChangedListeners = new CopyOnWriteArrayList<>();

    protected boolean isUseCache = true;

    private double mHeading;
    // 位置变化最小距离：当位置距离变化超过此值时，将更新位置信息（单位：米）
    protected float mMinimumUpdateDistance;
    // 位置信息更新周期（单位：毫秒）
    protected long mMinimumUpdateTime;

    private LocationPurpose mLocationPurpose;

    protected String cacheDir = null;

    private boolean hasAddress = true;

    protected LocationSource() {
        this.mStartStatus = new AtomicReference<>(Status.STOPPED);
    }

    public boolean isHasAddress() {
        return hasAddress;
    }

    public void setHasAddress(boolean hasAddress) {
        this.hasAddress = hasAddress;
    }

    @Override
    public void openCache() {
        isUseCache = true;
    }

    @Override
    public void closeCache() {
        isUseCache = false;
    }

    @Override
    public void clearLocationInfo() {
        mCurrentLocation = null;
        mLastKnownLocation = null;
    }

    @Override
    public boolean isLocationEnabled() {
        Context context = getContext();
        if (context == null) {
            return false;
        }
        LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
        return LocationSetting.isLocationEnabled(locationManager);
    }

    @Override
    public LocationSourceListener setLocationPurpose(LocationPurpose locationPurpose) {
        if (locationPurpose == null) {
            return this;
        }
        mLocationPurpose = locationPurpose;
        if (locationPurpose == LocationPurpose.SignIn) {
            this.mMinimumUpdateTime = 0;
            this.mMinimumUpdateDistance = 0f;
            return this;
        }
        if (locationPurpose == LocationPurpose.Sport) {
            this.mMinimumUpdateTime = 3000;
            this.mMinimumUpdateDistance = 10f;
            return this;
        }
        if (locationPurpose == LocationPurpose.Transport) {
            this.mMinimumUpdateTime = 1000;
            this.mMinimumUpdateDistance = 1f;
            return this;
        }
        return this;
    }

    @Override
    public void locationPurposeStart(LocationPurpose locationPurpose) {
        setLocationPurpose(locationPurpose);
        restartLocation();
    }

    @Override
    public void addHeadingChangedListener(LocationHeadingChangedListener listener) {
        if (!mHeadingChangedListeners.contains(listener)) {
            mHeadingChangedListeners.add(listener);
        }
    }

    @Override
    public void removeHeadingChangedListener(LocationHeadingChangedListener listener) {
        mHeadingChangedListeners.remove(listener);
    }

    @Override
    public final void addLocationChangedListener(final LocationChangedListener listener) {
        if (!mLocationChangedListeners.contains(listener)) {
            mLocationChangedListeners.add(listener);
        }
    }

    @Override
    public final void removeLocationChangedListener(LocationChangedListener listener) {
        mLocationChangedListeners.remove(listener);
    }

    @Override
    public final void addStatusChangedListener(final LocationStatusChangedListener listener) {
        if (!mStatusChangedListeners.contains(listener)) {
            mStatusChangedListeners.add(listener);
        }
    }

    @Override
    public final void removeStatusChangedListener(LocationStatusChangedListener listener) {
        mStatusChangedListeners.remove(listener);
    }

    @Override
    public final Throwable getError() {
        return this.mThrowable;
    }

    public void setThrowable(Throwable mThrowable) {
        this.mThrowable = mThrowable;
    }

    protected void setLastKnownLocation(LocationData mLastKnownLocation) {
        this.mLastKnownLocation = mLastKnownLocation;
    }

    protected final void setCurrentLocation(LocationData location) {
        this.mCurrentLocation = location;
    }

    protected void changedStatus(Status status) {
        mStartStatus.set(status);
        changedStatus();
    }

    private void changedStatus() {
        LocationEvent.StatusChangedEvent locationChangedEvent = new LocationEvent.StatusChangedEvent(this, mStartStatus.get());
        for (LocationStatusChangedListener listener : mStatusChangedListeners) {
            if (listener != null) {
                listener.statusChanged(locationChangedEvent);
            }
        }
    }

    public final void startForegroundLocation(Activity activity) {
        onStartForegroundLocation(activity);
    }

    protected abstract void onStartForegroundLocation(Activity activity);

    public final void stopForegroundLocation() {
        onStopForegroundLocation();
    }

    protected abstract void onStopForegroundLocation();

    @Override
    public void restartLocation() {
        stopLocation();
        Handler handler = new Handler(Looper.getMainLooper());
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                try {
                    startLocation();
                } finally {
                    handler.removeCallbacks(this);
                }
            }
        }, 1000);
    }

    public final void startLocation() {
        if (this.mStartStatus.get() == Status.STOPPED || this.mStartStatus.get() == Status.FAILED_TO_START) {
            this.mThrowable = null;
            onStart();
            changedStatus(Status.STARTING);
        } else {
            notifyLocation();
        }
    }

    protected abstract void onStart();

    public void stopLocation() {
        if (isStarted()) {
            onStop();
            changedStatus(Status.STOPPED);
        }
    }

    @Override
    public void closeLocation() {
        mLocationChangedListeners.clear();
        mStatusChangedListeners.clear();
        stopLocation();
        stopForegroundLocation();
    }

    protected abstract void onStop();

    /**
     * 更新点位
     *
     * @param location 点位
     */
    public final void updateLocation(final LocationData location) {
        if (location != null) {
            LocationData lastKnownLocation = getLocation();
            if (lastKnownLocation != null) {
                // 先记录上一次的点位
                setLastKnownLocation(lastKnownLocation);
                // 保存上一次的点位
                saveCacheLocation(getLastKnownLocationKey(), lastKnownLocation);
            } else {
                // 没有上一次点位，直接记录
                setLastKnownLocation(location);
                // 保存上一次的点位
                saveCacheLocation(getLastKnownLocationKey(), location);
            }
            // 设置本次的点位
            setCurrentLocation(location);
            // 保存本次的点位
            saveCacheLocation(getCurrentLocationKey(), location);
        }
        notifyLocation();
    }

    public final void updateHeading(final double header) {
        mHeading = header;
        LocationData location = getLocation();
        if (location != null) {
            location.header = mHeading;
        }
        for (LocationHeadingChangedListener listener : mHeadingChangedListeners) {
            listener.headingChanged(new LocationEvent.HeadingChangedEvent(this, header));
        }
    }


    @Override
    public void notifyLocation() {
        if (!isStarted()) {
            return;
        }
        LocationData location = getLocation();
        LocationEvent.LocationChangedEvent locationChangedEvent = new LocationEvent.LocationChangedEvent(this, location);
        for (LocationChangedListener listener : mLocationChangedListeners) {
            if (listener != null) {
                listener.locationChanged(locationChangedEvent);
            }
        }
        if (isStarted() && location != null && mLocationPurpose != null && mLocationPurpose == LocationPurpose.SignIn) {
            setLocationPurpose(LocationPurpose.Transport);
            stopLocation();
        }
    }

    @Override
    public void getOnceLocation(LocationOnceListener locationOnceListener) {
        if (locationOnceListener == null) {
            return;
        }
        // 这里要获取一次定位信息，不能获取
        mCurrentLocation = null;
        setLocationPurpose(LocationPurpose.SignIn);
        addLocationChangedListener(new LocationChangedListener() {
            @Override
            public void locationChanged(LocationEvent.LocationChangedEvent locationChangedEvent) {
                LocationData location = locationChangedEvent.getLocation();
                if (location == null) {
                    return;
                }
                double longitude = location.latLng.getLongitude();
                double latitude = location.latLng.getLatitude();
                if (longitude == 0.0 || latitude == 0.0) {
                    return;
                }
                locationOnceListener.onLocation(locationChangedEvent);
                removeLocationChangedListener(this);
                setLocationPurpose(LocationPurpose.Transport);
                stopLocation();
            }
        });
        restartLocation();
    }

    public LocationData getLocation() {
        if (mCurrentLocation != null) {
            return mCurrentLocation;
        }
        // 最新缓存数据不能大于10分钟
        LocationData cacheLocation = getCacheLocation(getCurrentLocationKey());
        if (cacheLocation == null) {
            return null;
        }
        long timeStamp = cacheLocation.time;
        long timeNow = System.currentTimeMillis();
        long time = timeNow - timeStamp;
        if (time > 1000 * 60 * 10) {
            // 超过10分钟，就不使用了
            return null;
        }
        return cacheLocation;
    }


    @Override
    public LocationData getLastKnownLocation() {
        if (mLastKnownLocation != null) {
            return mLastKnownLocation;
        }
        LocationData cacheLocation = getCacheLocation(getLastKnownLocationKey());
        if (cacheLocation == null) {
            return null;
        }
        long timeStamp = cacheLocation.time;
        long timeNow = System.currentTimeMillis();
        long time = timeNow - timeStamp;
        if (time > 1000 * 60 * 10) {
            // 超过10分钟，就不使用了
            return null;
        }
        return cacheLocation;
    }

    @Override
    public List<Address> getAddress() throws IOException {
        return getAddress(getLocation());
    }

    @SuppressLint("NewApi")
    @Override
    public List<Address> getAddress(LocationData locationData) throws IOException {
        if (!Geocoder.isPresent()) {
            return null;
        }
        if (locationData == null) {
            throw new IllegalArgumentException("Location data cannot be null.");
        }
        Geocoder geocoder = new Geocoder(getContext(), Locale.getDefault());
        return geocoder.getFromLocation(locationData.latLng.getLatitude(), locationData.latLng.getLongitude(), 1);
    }

    @Override
    public boolean isLatLngRepeated() {
        LocationData location = getLocation();
        if (location == null) {
            return false;
        }
        LocationData lastKnownLocation = getLastKnownLocation();
        if (lastKnownLocation == null) {
            return false;
        }
        // 判断本次和上一次经纬度是否一样
        return location.isRepeated(lastKnownLocation);
    }

    protected String getCurrentLocationKey() {
        return "currentSource-" + getCoordType().getName();
    }

    protected String getLastKnownLocationKey() {
        return "lastSource-" + getCoordType().getName();
    }


    protected void saveCacheLocation(String key, LocationData location) {
        if (location == null) {
            return;
        }
        if (TextUtils.isEmpty(cacheDir)) {
            initCacheFile(getContext());
        }
        try {
            LocationDataUtils.writeLocation(key, cacheDir, location.toJson());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    protected LocationData getCacheLocation(String key) {
        if (!isUseCache) {
            return null;
        }
        try {
            if (TextUtils.isEmpty(cacheDir)) {
                initCacheFile(getContext());
            }
            return LocationDataUtils.readLocation(getContext(), key, cacheDir);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    protected void initCacheFile(Context context) {
        if (context == null) {
            return;
        }
        File locationFile = context.getExternalFilesDir("");
        if (locationFile == null) {
            locationFile = context.getExternalCacheDir();
        }
        if (locationFile == null) {
            locationFile = context.getFilesDir();
        }
        if (locationFile != null) {
            cacheDir = locationFile.getAbsolutePath() + File.separator + "location";
        }
    }

    public abstract CoordinateType getCoordType();

    public boolean isStarted() {
        return mStartStatus.get() == Status.STARTING;
    }

    public static enum Status {
        STOPPED,
        STARTING,
        STARTED,
        FAILED_TO_START;

        private Status() {
        }
    }


//    public enum CoordType {
//
//        BD09LL("bd09ll"),//（百度经纬度坐标）
//        BD09MC("bd09mc"),//（百度墨卡托坐标）
//        GCJ02("gcj02"),//（经国测局加密的坐标）
//        WGS84("wgs84"),//（gps获取的原始坐标）
//        MAPBAR("MAPBAR"),//图吧坐标
//        MAPABC("MAPABC"),//图盟坐标
//        SOSOMAP("SOSOMAP"),//搜搜坐标
//        ALIYUN("ALIYUN"),//阿里云
//        GOOGLE("GOOGLE");//谷歌坐标
//
//        // 成员变量
//        private String name;
//
//        CoordType(String name) {
//            this.name = name;
//        }
//
//        public String getName() {
//            return name;
//        }
//
//    }


//    public static class Location implements Parcelable {
//
//        private LatLngSource latLngSource;
//        private double altitude = 0.0f;
//        private double accuracy = 0.0f;
//        private float speed = 0.0f;
//        private float bearing = 0.0f;
//        private double latitude = 0;
//        private double longitude = 0;
//        private String provider;
//        private long timeStamp;
//        private String coordType;
//
//        private double header;
//
//        public Location() {
//            initCoordType();
//        }
//
//        public Location(Location location) {
//            this(location.latitude, location.longitude);
//            altitude = location.getAltitude();
//            speed = location.getSpeed();
//            bearing = location.getBearing();
//            provider = location.getProvider();
//            accuracy = location.getAccuracy();
//            timeStamp = location.getTimeStamp();
//        }
//
//        public Location(android.location.Location androidLocation) {
//            this(androidLocation.getLatitude(), androidLocation.getLongitude());
//            speed = androidLocation.getSpeed();
//            bearing = androidLocation.getBearing();
//            provider = androidLocation.getProvider();
//            accuracy = androidLocation.getAccuracy();
//            if (androidLocation.hasAltitude()) {
//                altitude = androidLocation.getAltitude();
//            }
//            timeStamp = androidLocation.getTime();
//        }
//
//        public Location(double latitude, double longitude) {
//            initCoordType();
//            this.latitude = LatLngUtils.pointIntercept(latitude);
//            this.longitude = LatLngUtils.pointIntercept(longitude);
//            latLngSource = new LatLngSource(this.latitude, this.longitude);
//            timeStamp = System.currentTimeMillis();
//        }
//
//        public Location(double latitude, double longitude, double altitude, double accuracy, float speed, float bearing) {
//            this(latitude, longitude);
//            this.altitude = altitude;
//            this.accuracy = accuracy;
//            this.speed = speed;
//            this.bearing = bearing;
//        }
//
//        protected void initCoordType() {
//        }
//
//        public <T> T getLocationFor(Class<T> clazz) {
//            return null;
//        }
//
//        public LatLngSource getLatLngSource() {
//            return latLngSource;
//        }
//
//        public void setLatLngSource(LatLngSource latLngSource) {
//            this.latLngSource = latLngSource;
//        }
//
//        public double getAltitude() {
//            return altitude;
//        }
//
//        public void setAltitude(double altitude) {
//            this.altitude = altitude;
//        }
//
//        public double getAccuracy() {
//            return accuracy;
//        }
//
//        public void setAccuracy(double accuracy) {
//            this.accuracy = accuracy;
//        }
//
//        public float getSpeed() {
//            return speed;
//        }
//
//        public void setSpeed(float speed) {
//            this.speed = speed;
//        }
//
//        public float getBearing() {
//            return bearing;
//        }
//
//        public void setBearing(float bearing) {
//            this.bearing = bearing;
//        }
//
//        public double getLatitude() {
//            return latitude;
//        }
//
//        public void setLatitude(double latitude) {
//            this.latitude = latitude;
//        }
//
//        public double getLongitude() {
//            return longitude;
//        }
//
//        public void setLongitude(double longitude) {
//            this.longitude = longitude;
//        }
//
//        public String getProvider() {
//            return provider;
//        }
//
//        public void setProvider(String provider) {
//            this.provider = provider;
//        }
//
//        public long getTimeStamp() {
//            return timeStamp;
//        }
//
//        public void setTimeStamp(long timeStamp) {
//            this.timeStamp = timeStamp;
//        }
//
//        public String getCoordType() {
//            return coordType;
//        }
//
//        public void setCoordType(String coordType) {
//            this.coordType = coordType;
//        }
//
//        public double getHeader() {
//            return header;
//        }
//
//        public void setHeader(double header) {
//            this.header = header;
//        }
//
//        @Override
//        public String toString() {
//            return "Location{" +
//                    "latLngSource=" + latLngSource +
//                    ", altitude=" + altitude +
//                    ", accuracy=" + accuracy +
//                    ", speed=" + speed +
//                    ", bearing=" + bearing +
//                    ", latitude=" + latitude +
//                    ", longitude=" + longitude +
//                    ", provider='" + provider + '\'' +
//                    ", timeStamp=" + timeStamp +
//                    ", coordType='" + coordType + '\'' +
//                    ", header=" + header +
//                    '}';
//        }
//
//
//        public JSONObject toJson() {
//            try {
//                JSONObject jsonObject = new JSONObject();
//                jsonObject.put("longitude", longitude);
//                jsonObject.put("latitude", latitude);
//                jsonObject.put("provider", provider);
//                jsonObject.put("coordType", coordType);
//                jsonObject.put("timeStamp", timeStamp);
//                jsonObject.put("bearing", bearing);
//                jsonObject.put("speed", speed);
//                jsonObject.put("accuracy", accuracy);
//                jsonObject.put("altitude", altitude);
//                return jsonObject;
//            } catch (JSONException e) {
//                e.printStackTrace();
//            }
//            JSONObject jsonObject = new JSONObject();
//            try {
//                jsonObject.put("longitude", longitude);
//                jsonObject.put("latitude", latitude);
//            } catch (JSONException e) {
//                e.printStackTrace();
//            }
//            return jsonObject;
//        }
//
//        @Override
//        public int describeContents() {
//            return 0;
//        }
//
//        @Override
//        public void writeToParcel(Parcel dest, int flags) {
//            dest.writeParcelable(this.latLngSource, flags);
//            dest.writeDouble(this.altitude);
//            dest.writeDouble(this.accuracy);
//            dest.writeFloat(this.speed);
//            dest.writeFloat(this.bearing);
//            dest.writeDouble(this.latitude);
//            dest.writeDouble(this.longitude);
//            dest.writeString(this.provider);
//            dest.writeLong(this.timeStamp);
//            dest.writeString(this.coordType);
//            dest.writeDouble(this.header);
//        }
//
//        public void readFromParcel(Parcel source) {
//            this.latLngSource = source.readParcelable(LatLngSource.class.getClassLoader());
//            this.altitude = source.readDouble();
//            this.accuracy = source.readDouble();
//            this.speed = source.readFloat();
//            this.bearing = source.readFloat();
//            this.latitude = source.readDouble();
//            this.longitude = source.readDouble();
//            this.provider = source.readString();
//            this.timeStamp = source.readLong();
//            this.coordType = source.readString();
//            this.header = source.readDouble();
//        }
//
//        protected Location(Parcel in) {
//            this.latLngSource = in.readParcelable(LatLngSource.class.getClassLoader());
//            this.altitude = in.readDouble();
//            this.accuracy = in.readDouble();
//            this.speed = in.readFloat();
//            this.bearing = in.readFloat();
//            this.latitude = in.readDouble();
//            this.longitude = in.readDouble();
//            this.provider = in.readString();
//            this.timeStamp = in.readLong();
//            this.coordType = in.readString();
//            this.header = in.readDouble();
//        }
//
//        public static final Parcelable.Creator<Location> CREATOR = new Parcelable.Creator<Location>() {
//            @Override
//            public Location createFromParcel(Parcel source) {
//                return new Location(source);
//            }
//
//            @Override
//            public Location[] newArray(int size) {
//                return new Location[size];
//            }
//        };
//    }
}
